Skip to Content

05. 规划与执行系统设计

本章高频面试题

  1. 什么是 Planning?它和 Chain-of-Thought 有什么区别?
  2. ReAct、Plan-Then-Act、ReAct + 轻规划、Tree / Graph Planning 的区别是什么?
  3. 什么是 Deep Agents 模式?它和传统 ReAct 有何不同?
  4. 为什么很多 Agent 失败并不是模型不会推理,而是规划和执行系统设计得不对?
  5. 什么是 Durable Execution?为什么长时 Agent 离不开它?
  6. 一个生产级 Agent 的执行 runtime 应该负责什么?
  7. 如何设计可恢复、可重试、可重规划的多步执行系统?
  8. 如何处理工具超时、格式错误、部分失败、重复调用和并行步骤?
  9. 状态机、DAG、Graph 编排、循环执行各自适合什么场景?
  10. 如何防止 Agent 在执行过程中跑偏、卡死或无限循环?Circuit breaker 怎么设计?

1. 什么是 Planning

Planning 可以简单理解成:

在真正执行之前,先把任务拆成若干步骤,并明确每一步做什么、依赖什么、成功标准是什么。

它的关注点不是”解释我为什么这么想”,而是:

  • 下一步做什么
  • 先后顺序是什么
  • 失败了怎么办
  • 哪一步需要调用工具
  • 哪一步需要人来确认

因此,Planning 更接近”任务编排”,而不是普通的推理展开。

2. Planning 和 Chain-of-Thought 的区别

Chain-of-Thought 是模型把自己的推理过程写出来。 Planning 关注的是”可执行的任务结构”。

同一个任务:“调研 3 篇关于 RAG + RL 的论文并输出中文总结”

CoT 可能会写:

  • 我需要先找相关论文
  • 然后筛选出代表性的
  • 再总结它们的优缺点

Planning 更像:

  1. 用关键词检索论文
  2. 按时间和相关性筛选
  3. 对每篇抽取方法、实验和结论
  4. 生成中文对比总结
  5. 如果检索结果不足 3 篇,则扩展关键词再搜一次

一个好记的说法:

CoT 更像”把想法写出来”,Planning 更像”生成可以执行的任务表”。

3. 常见规划范式

3.1 ReAct

ReAct = Reason + Act:模型先思考当前应该做什么 → 调一个工具 → 看工具结果 → 再继续下一轮。

优点:简单、灵活、适合快速搭 Demo。 缺点:缺少全局前瞻、容易局部最优、任务一长就漂移、对多步失败恢复支持弱。

3.2 Plan-Then-Act

先生成较完整计划,再按计划执行。

优点:更可控、更适合长任务、便于审计和恢复。 缺点:环境变化时不够灵活,前面生成的计划可能很快过时。

3.3 ReAct + 轻规划

最实用的折中方案:先粗计划,执行时按阶段动态调整。 优点:比纯 ReAct 稳,比强规划灵活。 适合:中等复杂度任务、工具较多但还没有复杂搜索树的场景。

3.4 Tree / Graph Planning(ToT、LATS)

不是只走一条路径,而是探索多条候选路径再做选择。 优点:不容易卡死在第一条错误路径,适合高不确定性任务。 缺点:成本高、延迟高、实现复杂。 在工业里通常只用于高价值、高难度任务。

3.5 Deep Agents:2025 年流行起来的新范式

Deep Agents(LangChain 社区提出、Claude Code 在实践中验证)核心是三件套:

  1. 显式 Planning tool:让模型把 plan 写进一个可见、可修改的 todo_write / plan 工具,而不是藏在 CoT 里
  2. File system / scratchpad:中间结果、长文、草稿写到”虚拟文件系统”,主 context 只保留摘要和引用
  3. Sub-agents:把独立子任务派给 sub-agent,主 agent 只看汇报

为什么这套模式有效:

  • Plan 变成数据(可 diff、可 resume、可 eval),而不是模型一次性生成的 CoT
  • Context window 不再是瓶颈——长内容 offload 到 scratchpad
  • 主 agent 的 trajectory 保持短而清晰

Claude Code 能跑几千步不漂移,核心就是这套。任何 long-horizon task(代码编写、研究报告、深度调研)都可以借鉴。

4. 为什么规划与执行系统是 Agent 的核心

很多初学者会觉得:“模型够强,直接一步一步自己想就好了。”

但工程里真正的问题是:

  • 工具可能失败
  • 中间状态会变
  • 用户会打断任务
  • 有些步骤需要审批
  • 有些步骤能并行,有些不能
  • 进程可能崩溃
  • 机器可能重启

这些都不是单纯靠模型”想得更聪明”就能解决的。它们本质上是运行时设计问题

成熟 Agent 系统里:

  • 模型负责局部判断和内容生成
  • 运行时负责状态、重试、恢复、路由和持久化

5. Durable Execution:长时 Agent 的地基

如果你的 Agent 任务可能运行数分钟到数小时、可能跨多个进程、可能需要人工审批后继续,那就离不开 durable execution

5.1 Durable Execution 做了什么

核心能力:

  • 每一步状态都持久化:checkpoint 到存储,崩溃后能从最近 checkpoint 恢复
  • 决策重放确定性:同一个 state 喂给同一个 agent,行为应该可重现(至少对非 LLM 节点是这样)
  • Interrupt / resume:任务能在任意节点暂停,等待外部事件(人工审批、webhook)后恢复
  • Exactly-once 语义:副作用动作(发邮件、打款)即使 runtime 崩溃也不会重复执行
  • 长时等待不占资源:挂起的 task 只占存储,不占内存/连接

5.2 主流选型

  • LangGraph:内建 checkpointer(MemorySaver、PostgresSaver、RedisSaver 等)、原生 interrupt() / Command(resume=...) 模式、天然适配 LLM agent
  • Temporal:老牌 durable execution 平台,跨语言、成熟,SDK 覆盖 TS/Python/Go/Java
  • Inngest:偏事件驱动、serverless 友好、TS 生态好
  • Restate:较新的 durable execution runtime,主打轻量和低延迟
  • 自研 event-sourced state machine:当业务需求非常定制、不想引入新依赖时

选型原则:

  • 主 agent 在 LangGraph,副作用动作(发邮件、打款、publish)外挂到 Temporal/Inngest 这类专业 durable queue,两者互补
  • 不要把 durable execution 当成简单的”重试库”——核心价值是 state-as-data

6. 执行系统应该承担哪些职责

一个生产级执行 runtime 至少应该负责:

  1. 持有当前状态(结构化、可持久化)
  2. 按规则调度节点或步骤
  3. 调模型和工具
  4. 超时、重试、降级
  5. 记录事件和日志
  6. 支持中断和恢复(interrupt / resume)
  7. 支持人工审批
  8. 输出可观察 trace
  9. 副作用幂等(idempotency key、去重)
  10. Budget 控制(步数、token、花费、延迟的 circuit breaker)

换句话说,执行系统不是”包一层 while 循环”那么简单。

7. 执行状态该怎么设计

7.1 不要只依赖 message history

很多 Agent Demo 只依赖消息列表,但一旦任务复杂:

  • 历史消息不等于任务状态
  • 历史消息不便于校验
  • 历史消息不便于恢复

更稳的做法是显式定义结构化状态:

type TaskStatus = | "pending" | "running" | "waiting_human" | "completed" | "failed" | "cancelled"; type AgentRunState = { runId: string; objective: string; plan: Array<{ id: string; title: string; status: "pending" | "running" | "done" | "failed" | "skipped"; retryCount: number; dependsOn: string[]; idempotencyKey?: string; }>; currentStepId?: string; status: TaskStatus; errors: Array<{ stepId?: string; code: string; // 结构化错误码,不是自由文本 message: string; retryable: boolean; retryAfterMs?: number; }>; budget: { maxSteps: number; maxTokens: number; maxCostUsd: number; usedSteps: number; usedTokens: number; usedCostUsd: number; }; approvedActions: string[]; };

好处:

  • 更容易恢复
  • 更容易做事件流
  • 更容易判断”现在到底在哪一步”
  • 更容易做 budget 控制

7.2 把状态和消息分开

推荐的做法:

  • messages 用来和模型对话
  • state 用来驱动任务执行

LangGraph 的 State + MessagesValue 模式就是这个思路的具体化。

8. 规划与执行的常见架构选择

8.1 线性步骤流

适合:明确固定流程、小规模 MVP。 优点:简单。缺点:很难表达复杂分支。

8.2 DAG / Workflow

适合:依赖关系明确、某些步骤可以并行。 优点:可视化清晰、并行友好。缺点:对复杂动态回环不够自然。

8.3 Graph / State Machine

适合:长任务、复杂分支、需要中断恢复、混合人工审批。 优点:控制力强,更适合真实 Agent runtime。 代表:LangGraph、XState、自研状态机。

8.4 Plan-as-Data

最现代的做法:把 plan 当成结构化数据(JSON schema 定义的数组),模型用 update_plan / complete_step 这种工具去读写它。plan 可以 diff、可 resume、可 eval,这就是 Deep Agents 的核心思路。

9. 如何处理失败:重试、回滚、重规划

生产系统里最关键的部分之一。

9.1 先区分错误类型

  1. 临时错误:网络波动、超时、429 → 退避重试
  2. 数据错误:返回格式不合法、缺字段、工具返回空 → schema 级重试或换策略
  3. 逻辑错误:选错工具、计划不合理、前后依赖破坏 → 重规划
  4. 外部系统错误:下游 API 挂了 → 降级或切换路径
  5. 预算错误:超步数/超成本 → 直接中断,不重试

不同错误不应该一律”重试三次”。

9.2 可重试错误的规范做法

  1. 记录失败事件(结构化错误码)
  2. 指数退避 + jitter(避免雪崩)
  3. 尊重 retry_after_ms(工具返回的限流提示)
  4. idempotency key(避免重复副作用)
  5. 超过阈值后进入降级或重规划
  6. Circuit breaker:同一下游连续失败 N 次 → 整个 circuit 打开一段时间

9.3 不可重试错误

例如参数错误、权限不足、schema 不匹配。继续重试没有意义:

  • 直接失败
  • 或进入局部重规划

9.4 局部重规划优先于全局重来

例如任务是:1) 搜论文 → 2) 获取全文 → 3) 总结

如果某篇 PDF 抓取失败,不应该整条链路重来,而是:

  • 切到摘要模式
  • 或换一篇候选论文

9.5 Critic / Reflection loop

生产系统里常见的一个模式是加一个”批评者”节点:

  • 每 N 步或每个 milestone 后,让一个轻量 LLM 或 rule engine 检查当前进度
  • 判断:“是否还在解决原问题""是否已经偏离目标""前几步是否有重复劳动”
  • 偏离时触发 replan 或 abort

这是 Reflexion 论文的核心思路,也是 Deep Agents 里的常见做法。

10. 如何避免无限循环和任务漂移(Circuit Breaker)

ReAct 类 Agent 最常见的问题。推荐的多层 circuit breaker:

10.1 硬性预算

  • 最大步数(典型 50-200)
  • 最大 token 消耗
  • 最大花费(美元)
  • 最大 wall-clock 时间

任一超限立即中断 run。

10.2 工具级约束

  • 同一 tool 连续调用 N 次以上告警 → 可能陷入循环
  • 同一 tool 在整个 run 中累计 > M 次告警 → 可能缺少外部信息源
  • 同一参数组合重复调用直接拒绝(缓存上次结果)

10.3 Progress assessment

每 K 步强制 agent 输出”当前目标 / 已完成 / 下一步 / 预期剩余步数”。连续几轮”预期剩余步数”不下降就触发 replan。

10.4 Deadlock detection

如果 state.plan 所有 pending 步骤都依赖未完成的前置步骤,且最近 M 步没有新的完成事件 → 死锁,人工介入。

核心原则

不要把”停止”完全交给模型自己决定。

11. 并行执行怎么设计

并行不是越多越好。先判断步骤之间有没有依赖。

适合并行:检索多个来源、并发抓取多个候选文档、对多个独立对象做同类分析。 不适合并行:下游依赖上游结果、需要顺序审批、会互相争抢共享资源。

并行执行的工程要点:

  • Concurrency pool / semaphore:限制并发数,避免打爆下游
  • 超时:每个并行任务独立超时,聚合时不被最慢的一个拖死(可以用 Promise.allSettled + 超时包装)
  • Failure isolation:一个子任务失败不应该让整个 gather 崩掉
  • Backpressure:聚合层处理不过来时下发限流
  • Rate limit awareness:并行发 API 要遵守 provider 的 RPM/TPM 限制

12. TypeScript 示例:带重试 + 幂等 + budget 的执行器

type StepResult<T> = | { ok: true; value: T; tokensUsed?: number; costUsd?: number } | { ok: false; retryable: boolean; retryAfterMs?: number; error: string; code: string; }; type PlanStep<T> = { id: string; title: string; idempotencyKey: string; run: (ctx: { idempotencyKey: string }) => Promise<StepResult<T>>; maxRetries?: number; }; type Budget = { remainingSteps: number; remainingTokens: number; remainingCostUsd: number; }; export async function executeStep<T>( step: PlanStep<T>, budget: Budget ): Promise<T> { if (budget.remainingSteps <= 0) { throw new Error("budget_exhausted: steps"); } budget.remainingSteps -= 1; const maxRetries = step.maxRetries ?? 2; let attempt = 0; while (attempt <= maxRetries) { const result = await step.run({ idempotencyKey: step.idempotencyKey }); if (result.ok) { budget.remainingTokens -= result.tokensUsed ?? 0; budget.remainingCostUsd -= result.costUsd ?? 0; return result.value; } if (!result.retryable) { throw new Error(`[${step.id}] ${result.code}: ${result.error}`); } if (attempt === maxRetries) { throw new Error(`[${step.id}] retries_exhausted: ${result.error}`); } const backoff = result.retryAfterMs ?? Math.min(30_000, 300 * 2 ** attempt + Math.random() * 200); await sleep(backoff); attempt += 1; } throw new Error(`[${step.id}] unreachable`); } function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); }

体现的工程原则:

  • 区分可重试 / 不可重试(retryable 字段)
  • 错误码结构化(code),不是自由文本
  • 指数退避 + jitter
  • 尊重上游 retry_after_ms
  • 幂等 key 显式传入,跨重试保持同一个
  • Budget 显式追踪,超限直接中断

13. 什么时候用 LangGraph 这类框架

当执行系统需要下面这些能力,就值得考虑图式 runtime:

  • 长时间运行
  • 持久化恢复(checkpoint)
  • interrupt() / Command(resume=...)
  • 多分支和复杂回环
  • human-in-the-loop
  • 更强的事件流(stream modes: values / updates / messages / custom / tools / debug)

LangGraph 的 interrupt 模式(2025 版)把 HITL 做得很干净:节点内调 interrupt(payload) 暂停执行,状态持久化到 checkpointer,外部决策到位后用 graph.invoke(new Command({ resume: decision }), { configurable: { thread_id } }) 继续。同一 thread 可以暂停数月后在另一台机器上恢复。

14. 本章方法论小结

  1. Planning 是可执行任务编排,不等于 CoT;把 plan 做成数据而不是推理展开
  2. Deep Agents 模式(planning tool + scratchpad + sub-agents)是长任务的当前最佳实践
  3. Agent 可靠性很大程度取决于执行 runtime,而不是只取决于模型
  4. Durable execution 是长时 agent 的地基:state-as-data、checkpoint、interrupt/resume、exactly-once
  5. 显式状态比纯消息历史更适合复杂任务,状态必须包含 idempotency keybudget
  6. 失败处理要区分错误类型,优先局部重规划;可重试错误走指数退避 + jitter + idempotency
  7. 多层 circuit breaker(步数/token/成本/工具调用次数/progress assessment)是避免失控的硬保障
  8. 并行要配 concurrency pool、独立超时、failure isolation、backpressure
  9. Graph / State Machine 更适合复杂、长时间运行的 Agent;副作用动作外挂到 Temporal/Inngest 最稳
  10. 一个成熟 Agent 的执行系统必须支持重试、恢复、事件、审计、budget 和 exactly-once
Last updated on